﻿using System.ComponentModel;
using System.Drawing;
using System.Drawing.Printing;

using Storm.TextEditor.Parser.Objects;
using Storm.TextEditor.Parser.Objects.Collections;

namespace Storm.TextEditor.Editor.Printing
{
	/// <summary>
	/// Defines a reusable object that sends output to a printer, when printing from a SyntaxDocument.
	/// </summary>
	[ToolboxItem(true)]
	public class CodePrintDocument
		: PrintDocument
	{
		#region Fields

		private Font fontNormal = null;
		private Font fontBreak  = null;
		private int  rowIndex   = 0;

		private SyntaxDocument document      = null;
		private RowCollection  rowCollection = null;

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the document that the CodePrintDocument should print.
		/// </summary>
		public SyntaxDocument Document
		{
			get { return document; }
			set { document = value; }
		}

		#endregion

		#region Methods

		#region Protected

		/// <summary>
		/// Override OnBeginPaint to re-initialize fields of the CodePrintDocument when it is about to print.
		/// </summary>
		/// <param name="ev">PrintEventArgs.</param>
		protected override void OnBeginPrint(PrintEventArgs ev)
		{
			fontNormal = new Font("Courier new", 8, FontStyle.Regular);
			fontBreak  = new Font("Courier New", 8, FontStyle.Bold);
			rowIndex   = 0;

			base.OnBeginPrint(ev);
		}

		/// <summary>
		/// Override OnPrintPage to execute logic for printing a SyntaxDocument.
		/// </summary>
		/// <param name="ev">PrintPageEventArgs.</param>
		protected override void OnPrintPage(PrintPageEventArgs ev)
		{
			float maximumHeight = 0;
			int   currentIndex  = 0;

			float leftMargin  = ev.MarginBounds.Left;
			float rightMargin = ev.MarginBounds.Right;
			float topMargin   = ev.MarginBounds.Top;

			float y = 0;

			if (rowCollection == null)
			{
				Document.ParseAll();
				Document.ParseAll(true);

				rowCollection = new RowCollection();
				foreach (Row row in this.Document)
				{
					bool  rowBreak = false;
					float x        = leftMargin;

					Row newRow = new Row();
					rowCollection.Add(newRow);

					foreach (Word word in row)
					{
						Font font = fontNormal;
						if (word.Style != null)
						{
							FontStyle fontStyle = 0;

							if (word.Style.Bold == true)
								fontStyle |= FontStyle.Bold;

							if (word.Style.Italic == true)
								fontStyle |= FontStyle.Italic;

							if (word.Style.Underline == true)
								fontStyle |= FontStyle.Underline;

							font = new Font("Courier new", 8, fontStyle);
						}

						SizeF wordSize = ev.Graphics.MeasureString(word.Text, font);
						if (x + wordSize.Width > rightMargin)
						{
							Word breakWord      = new Word();
							char breakCharacter = (char)0xbf;
							breakWord.Text = breakCharacter + "";

							newRow.Add(breakWord);
							rowBreak = true;

							newRow = new Row();
							rowCollection.Add(newRow);
							x = leftMargin;
						}

						x += wordSize.Width;
						newRow.Add(word);
					}

					if (rowBreak == true)
						rowCollection.Add(new Row());
				}
			}

			maximumHeight = ev.MarginBounds.Height / fontNormal.GetHeight(ev.Graphics);
			while (currentIndex < maximumHeight && (rowIndex < rowCollection.Count))
			{
				float x = leftMargin;
				y     = topMargin + (currentIndex * fontNormal.GetHeight(ev.Graphics));

				Row row = rowCollection[rowIndex];
				foreach (Word word in row)
				{
					if (word.Text == ((char)0xbf) + "")	// Word is a break word.
						ev.Graphics.DrawString(word.Text, fontBreak, Brushes.Black, x, y, new StringFormat());
					else
					{
						SizeF wordSize = ev.Graphics.MeasureString(word.Text, fontNormal);
						if (word.Text != null && (".,:;".IndexOf(word.Text) >= 0))
						{
							wordSize.Width = 6;
							x -= 4;
						}

						if (word.Text == "\t")
							wordSize.Width = ev.Graphics.MeasureString("...", fontNormal).Width;

						Color foreColor = Color.Black;
						Font  font      = fontNormal;

						if (word.Style != null)
						{
							foreColor           = word.Style.ForeColor;
							FontStyle fontStyle = 0;

							if (word.Style.Bold == true)
								fontStyle |= FontStyle.Bold;

							if (word.Style.Italic == true)
								fontStyle |= FontStyle.Italic;

							if (word.Style.Underline == true)
								fontStyle |= FontStyle.Underline;

							font = new Font("Courier new", 8, fontStyle);

							if (word.Style.Transparent == false)
							{
								Color backColor = word.Style.BackColor;
								ev.Graphics.FillRectangle(new SolidBrush(backColor), x, y, wordSize.Width, fontNormal.GetHeight(ev.Graphics));
							}
						}

						foreColor = Color.FromArgb(foreColor.R, foreColor.G, foreColor.B);	// Refresh.
						ev.Graphics.DrawString(word.Text, font, new SolidBrush(foreColor), x, y, new StringFormat());
						x += wordSize.Width;
					}
				}

				currentIndex++;
				rowIndex++;
			}

			if (rowIndex < rowCollection.Count)
				ev.HasMorePages = true;
			else
				ev.HasMorePages = false;

			base.OnPrintPage(ev);
		}

		#endregion

		#endregion

		/// <summary>
		/// Initializes a new instance of CodePrintDocument.
		/// </summary>
		public CodePrintDocument()
			: base()
		{
		}

		/// <summary>
		/// Initializes a new instance of CodePrintDocument.
		/// </summary>
		public CodePrintDocument(SyntaxDocument document)
			: base()
		{
			this.Document = document;
		}
	}
}
